/*
 * 人员部件显示驱动程序 - 为PersonPart实现显示和编辑逻辑
 * 
 * 主要功能：
 * - 为PersonPart实现显示形状生成
 * - 提供人员部件的编辑界面
 * - 实现模型绑定和验证
 * - 支持不同显示类型的布局
 */

using Lombiq.TrainingDemo.Models;
using Lombiq.TrainingDemo.ViewModels;
using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.ContentManagement.Display.Models;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using System.Threading.Tasks;

namespace Lombiq.TrainingDemo.Drivers;

/// <summary>
/// 人员部件显示驱动程序 - 为PersonPart实现显示和编辑逻辑
/// 
/// 主要用途：
/// - 为PersonPart实现显示形状生成
 /* - 提供人员部件的编辑界面
 * - 实现模型绑定和验证
 * - 支持不同显示类型的布局
 * 
 * 使用场景：
 * - 显示人员信息（姓名、出生日期、惯用手）
 * - 编辑人员部件数据
 * - 在不同显示类型中布局内容
 * - 实现数据验证和模型绑定
 * 
 * 设计原因：
 * - 为内容部件提供完整的显示和编辑支持
 * - 实现部件级别的数据验证
 * - 支持灵活的显示布局控制
 * - 提供可重用的部件编辑组件
 * 
 * 设计模式：
 * - 驱动模式：继承ContentPartDisplayDriver实现部件逻辑
 * - 视图模型模式：使用专门的ViewModel传递数据
 * - 位置模式：使用Location定义显示位置
 * - 验证模式：实现自定义验证逻辑
/// </summary>
// 继承自ContentPartDisplayDriver的驱动程序具有与BookDisplayDriver中描述的功能类似的功能，
// 但这些是用于ContentParts的。不要忘记在服务提供程序中注册此类（请参阅：Startup.cs）。*/
public sealed class PersonPartDisplayDriver : ContentPartDisplayDriver<PersonPart>
{
    /// <summary>
    /// 显示方法 - 为PersonPart生成显示形状
    /// 
    /// 功能说明：
    /// - 使用Initialize帮助器创建显示形状
    /// - 使用PersonPartViewModel传递数据
    /// - 定义在不同显示类型中的位置
    /// - 支持Detail和Summary显示类型
    /// 
    /// 显示类型：
    /// - Detail：在前端完整打开内容项时使用
    /// - Summary：在前端列出时使用
    /// - SummaryAdmin：在管理站点列出时使用
    /// 
    /// 位置设置：
    /// - Content:1 - 在内容区域的第一位置显示
    /// - 权重1确保部件数据在Text Field的Biography之上
    /// </summary>
    /// <param name="part">要显示的PersonPart</param>
    /// <param name="context">部件显示构建上下文</param>
    /// <returns>显示结果</returns>
    public override IDisplayResult Display(PersonPart part, BuildPartDisplayContext context) =>
        // 这里您有一个带有形状名称和工厂的形状帮助器。Initialize方法将从作为泛型参数给定的类型实例化视图模型。
        // 建议像我们在这里做的那样为视图使用视图模型（有时您会想要为Display()和Edit()使用单独的视图模型）。
        // 有一些帮助器方法来生成形状类型。在这种情况下，GetDisplayShapeType()将默认生成"PersonPart"，
        // 但这可以从管理界面内容类型设置下的部件设置中覆盖。在工厂中，我们将内容部件属性映射到视图模型；
        // 如果需要任何繁重的工作来生成视图模型（比如从数据库或外部API获取数据），那么就在那个工厂中做。
        // 这样，只有在实际显示形状时才会完成工作。
        Initialize<PersonPartViewModel>(GetDisplayShapeType(context), viewModel => PopulateViewModel(part, viewModel))
            // 注意，我们再次在这里引用显示类型，就像Books一样，Detail显示类型。虽然显示类型可以自定义，
            // 但默认情况下，当内容项在前端完整打开时，Orchard使用"Detail"，在前端列出时使用"Summary"，
            // 在管理站点列出时使用"SummaryAdmin"。您可以使用这些位置设置来指定在这些场景中如何显示内容部件。
            // 在这个简单的情况下，Detail和Summary将使用相同的形状。

            // 下一站：快速查看PersonPart.cshtml然后回到这里。

            // 注意，我们需要权重1来将部件的数据放在来自Text Field的Biography之上。
            .Location("Detail", "Content:1")
            .Location("Summary", "Content:1");

    /// <summary>
    /// 编辑方法 - 为PersonPart生成编辑形状
    /// 
    /// 功能说明：
    /// - 使用Initialize帮助器创建编辑形状
    /// - 使用PersonPartViewModel传递数据
    /// - 支持通过placement.json文件定义位置
    /// - 使用GetEditorShapeType生成形状类型
    /// 
    /// 位置定义：
    /// - 可以通过placement.json文件定义位置
    /// - 支持特定字段、内容类型、路径的规则匹配
    /// - 使用区分符属性避免位置冲突
    /// </summary>
    /// <param name="part">要编辑的PersonPart</param>
    /// <param name="context">部件编辑器构建上下文</param>
    /// <returns>编辑结果</returns>
    public override IDisplayResult Edit(PersonPart part, BuildPartEditorContext context) =>
        // 与Display方法类似的事情发生。GetEditorShapeType()将默认生成"PersonPart_Edit"。

        // 注意，这个形状没有给出位置（没有Location()方法调用）。还有另一个选项是使用Placement.json文件给出这些位置。
        // 由于不可能在.json文件中放置注释，解释在这里，但请确保在阅读时检查文件。重要的是在某个地方给出位置，
        // 否则形状不会显示。形状文件默认应该在Views文件夹中，但是，它也可以在Views文件夹之外（例如在Drivers文件夹内）。

        // 在Placement文件中，您可以为Orchard生成的任何形状提供特定位置。您还可以指定规则来匹配何时应用位置：
        // 比如只针对某些字段、内容类型，或者只在给定路径下。在我们的Placement文件中，您可以看到PersonPart形状
        // 在Content区域获得第一个位置，TextField形状获得第二个位置。为确保不是所有TextField都获得相同位置，
        // 给出了一个"区分符"属性，它引用字段附加到的部件名称和字段名称。请确保也阅读文档以更好地了解此功能：
        // https://docs.orchardcore.net/en/latest/docs/reference/core/Placement/#placement-files

        // 下一站：placement.json（需要小写）然后回到这里。
        Initialize<PersonPartViewModel>(GetEditorShapeType(context), viewModel => PopulateViewModel(part, viewModel));

    /// <summary>
    /// 更新方法 - 处理PersonPart的更新逻辑
    /// 
    /// 功能说明：
    /// - 使用IUpdateModel进行模型绑定
    /// - 创建视图模型并填充数据
    /// - 执行验证逻辑
    /// - 将视图模型数据映射回部件
    /// 
    /// 更新流程：
    /// 1. 创建视图模型并绑定数据
    /// 2. 执行验证（如有需要）
    /// 3. 将视图模型数据映射到部件
    /// 4. 返回编辑结果
    /// 
    /// 前缀使用：
    /// - Prefix属性用于区分同名输入字段
    /// - 默认使用内容部件名称
    /// - 多个驱动程序需要覆盖此属性
    /// </summary>
    /// <param name="part">要更新的PersonPart</param>
    /// <param name="context">更新上下文</param>
    /// <returns>更新结果</returns>
    public override async Task<IDisplayResult> UpdateAsync(PersonPart part, UpdatePartEditorContext context)
    {
        // 通过IUpdateModel，您将能够在驱动程序中使用当前控制器的模型绑定帮助器。Prefix属性将用于在构建编辑器表单时
        // 区分同名输入字段（例如，组成内容项的两个内容部件可以有一个名为"Name"的输入字段）。默认情况下，
        // Orchard Core将使用内容部件名称，但如果您有多个带有编辑器的驱动程序用于内容部件，您需要在驱动程序中覆盖它。
        var viewModel = await context.CreateModelAsync<PersonPartViewModel>(Prefix);

        // 现在如果需要，您可以进行一些验证。一种方法是简单地在这里编写自己的验证，或者您可以在视图模型类中做。

        // 去查看ViewModels/PersonPartViewModel以了解如何做，然后回到这里。

        // 最后将视图模型映射到内容部件。默认情况下，如果有验证错误，这些更改不会被持久化。否则，这些将自动存储在数据库中。
        part.BirthDateUtc = viewModel.BirthDateUtc;
        part.Name = viewModel.Name;
        part.Handedness = viewModel.Handedness;

        return await EditAsync(part, context);
    }

    /// <summary>
    /// 填充视图模型 - 将部件数据映射到视图模型
    /// 
    /// 功能说明：
    /// - 将PersonPart的属性映射到视图模型
    /// - 设置出生日期、姓名和惯用手
    /// - 建立部件和视图模型之间的关联
    /// 
    /// 映射属性：
    /// - BirthDateUtc：出生日期
    /// - Name：姓名
    /// - Handedness：惯用手
    /// </summary>
    /// <param name="part">源PersonPart</param>
    /// <param name="viewModel">目标PersonPartViewModel</param>
    private static void PopulateViewModel(PersonPart part, PersonPartViewModel viewModel)
    {
        viewModel.PersonPart = part;

        viewModel.BirthDateUtc = part.BirthDateUtc;
        viewModel.Name = part.Name;
        viewModel.Handedness = part.Handedness;
    }
}

// 下一站：Controllers/PersonListController并回到我们离开的OlderThan30方法。
